// Package service
/**
* @Project : geektime-basic-go-study
* @File    : user.go
* @IDE     : GoLand
* @Author  : Tvux
* @Date    : 2024/9/5 13:07
**/

package service

import (
	"context"
	"errors"
	"geektime-basic-go-study/webook/backend/internal/domain"
	"geektime-basic-go-study/webook/backend/internal/repository"
	"golang.org/x/crypto/bcrypt"
)

var (
	ErrUserDuplicate = repository.ErrUserDuplicate
	ErrUserNotFound  = repository.ErrUserNotFound
	// ErrInvalidEmailOrPassword 注意：这里不能明确告诉用户具体是邮箱还是密码不正确，不安全
	ErrInvalidEmailOrPassword = errors.New("邮箱或密码错误")
)

type UserService interface {
	SignUp(ctx context.Context, u domain.User) error
	Login(ctx context.Context, u domain.User) (domain.User, error)
	Profile(ctx context.Context, id int64) (domain.User, error)
	FindOrCreateByPhone(ctx context.Context, phone string) (domain.User, error)
	FindOrCreateByWechat(ctx context.Context, wechatUI domain.WechatUserInfo) (domain.User, error)
	UpdateNonSensitiveInfo(ctx context.Context, u domain.User) error
}

var (
	_ UserService = (*userService)(nil)
)

type userService struct {
	repo repository.UserRepository
}

func NewUserService(repo repository.UserRepository) UserService {
	return &userService{
		repo: repo,
	}
}

func (Self *userService) SignUp(ctx context.Context, u domain.User) error {
	encryptedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	u.Password = string(encryptedPassword)
	return Self.repo.Create(ctx, u)
}

func (Self *userService) Login(ctx context.Context, u domain.User) (domain.User, error) {
	foundUser, err := Self.repo.FindUserByEmail(ctx, u.Email)
	if err != nil {
		if errors.Is(err, ErrUserNotFound) {
			return domain.User{}, ErrInvalidEmailOrPassword
		} else {
			return domain.User{}, err
		}
	}
	err = bcrypt.CompareHashAndPassword([]byte(foundUser.Password), []byte(u.Password))
	if err != nil {
		// LOG DEBUG
		return domain.User{}, ErrInvalidEmailOrPassword
	}
	return foundUser, nil
}

func (Self *userService) Profile(ctx context.Context, id int64) (domain.User, error) {
	return Self.repo.FindUserById(ctx, id)
}

func (Self *userService) FindOrCreateByPhone(ctx context.Context, phone string) (domain.User, error) {
	// 这里是 快路径
	u, err := Self.repo.FindUserByPhone(ctx, phone)
	if !errors.Is(err, repository.ErrUserNotFound) {
		// 因为绝大部分用户会命中这个分支
		// err == nil or err != repository.ErrUserNotFound
		return u, err
	}
	// err == repository.ErrUserNotFound

	// 如果系统要崩溃了，可以考虑不做 慢路径 的业务
	//if ctx.Value("降级") == "true" {
	//	return domain.User{}, errors.New("系统降级")
	//}
	// 这里是 慢路径

	// 确实没有该用户，创建该用户
	u = domain.User{
		Phone: phone,
	}
	err = Self.repo.Create(ctx, u)
	if err != nil && !errors.Is(err, ErrUserDuplicate) {
		return u, err
	}
	// 这里会有主从延迟的问题：迁移成微服务架构之后，会从单库转为主从，此时这部分代码会有问题
	// 可能的修改方法是让 Create 方法返回创建出来的用户ID
	return Self.repo.FindUserByPhone(ctx, phone)
}

func (Self *userService) FindOrCreateByWechat(ctx context.Context, wechatUI domain.WechatUserInfo) (domain.User, error) {
	// 这里是 快路径
	u, err := Self.repo.FindUserByWechat(ctx, wechatUI.OpenId)
	if !errors.Is(err, repository.ErrUserNotFound) {
		// 因为绝大部分用户会命中这个分支
		// err == nil or err != repository.ErrUserNotFound
		return u, err
	}
	// err == repository.ErrUserNotFound

	// 如果系统要崩溃了，可以考虑不做 慢路径 的业务
	//if ctx.Value("降级") == "true" {
	//	return domain.User{}, errors.New("系统降级")
	//}
	// 这里是 慢路径

	// 确实没有该用户，创建该用户
	u = domain.User{
		WechatInfo: wechatUI,
	}
	err = Self.repo.Create(ctx, u)
	if err != nil && !errors.Is(err, ErrUserDuplicate) {
		return u, err
	}
	// 这里会有主从延迟的问题：迁移成微服务架构之后，会从单库转为主从，此时这部分代码会有问题
	// 可能的修改方法是让 Create 方法返回创建出来的用户ID
	return Self.repo.FindUserByWechat(ctx, wechatUI.OpenId)
}

func (Self *userService) UpdateNonSensitiveInfo(ctx context.Context, u domain.User) error {
	// 以防 web 层修改敏感数据
	Self.maskSensitiveInfo(&u)
	return Self.repo.Update(ctx, u)
}

// maskSensitiveInfo 清除用户对象中的敏感信息。
func (Self *userService) maskSensitiveInfo(u *domain.User) {
	u.Email = ""
	u.Phone = ""
	u.Password = ""
}
